//-----------------------------------------------------------------------------
//  Copyright (C) 2011-2018, GB Research, LLC (www.gbresearch.com)
//  
//  Boost Software License - Version 1.0 - August 17th, 2003
//
//  Permission is hereby granted, free of charge, to any person or organization
//  obtaining a copy of the software and accompanying documentation covered by
//  this license (the "Software") to use, reproduce, display, distribute,
//  execute, and transmit the Software, and to prepare derivative works of the
//  Software, and to permit third-parties to whom the Software is furnished to
//  do so, all subject to the following:
//
//  The copyright notices in the Software and this entire statement, including
//  the above license grant, this restriction and the following disclaimer,
//  must be included in all copies of the Software, in whole or in part, and
//  all derivative works of the Software, unless such copies or derivative
//  works are solely in the form of machine-executable object code generated by
//  a source language processor.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
//  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
//  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#pragma once

#include <string>
#include <string_view>
#include <sstream>
#include <ostream>
#include <utility>
#include <iterator>
#include <cstddef>
#include "axe_result.h"
#include "axe_trait.h"
#include "axe_iterator.h"
#include "axe_detail.h"
#include "axe_composite.h"

namespace axe
{

    //-------------------------------------------------------------------------
    /// rule extracts iterator range from matching R and passes it to E
    //-------------------------------------------------------------------------
    template<class R, class E>
    class r_extractor_t 
    {
        R r_; // rule
        E e_; // extractor

    public:
		template<class RR, class EE>
        r_extractor_t(RR&& r, EE&& e) : r_(std::forward<RR>(r)), e_(std::forward<EE>(e)) {}

        //---------------------------------------------------------------------
        template<class Iterator, class Iterator2>
        auto operator() (Iterator i1, Iterator2 i2)const -> decltype(r_(i1, i2))
        {
            static_assert(is_extractor_object_v<E>
                || is_extractor_object_v<E, Iterator>
                || is_extractor_object_v<E, Iterator, Iterator>
                || is_extractor_object_v<E, Iterator, Iterator, Iterator>);

            auto i = r_(i1, i2);
            
            if(i.matched)
            {
                if constexpr(is_extractor_object_v<E>)
                    std::invoke(e_);
                else if constexpr(is_extractor_object_v<E, Iterator>)
                    std::invoke(e_, i.position);
                else if constexpr(is_extractor_object_v<E, Iterator, Iterator>)
                    std::invoke(e_, i1, i.position);
                else if constexpr(is_extractor_object_v<E, Iterator, Iterator, Iterator>)
                    std::invoke(e_, i1, i.position, i2);
            }

            return i;
        }
    #if defined(AXE_ALLOW_PARSE_TREE_EXTRACTION)
        //---------------------------------------------------------------------
        template<class Iterator>
        auto operator() (const it_pair<Iterator>& itp)const -> detail::parse_tree_result_t<R, Iterator>
        {
            using data_t = detail::parse_tree_data_t<R, Iterator>;
            static_assert(is_extractor_object_v<E, data_t>);

            auto res = detail::parse_tree_invoke(r_, itp);

            if (res.matched)
            {
                std::invoke(e_, res.data);
            }

            return res;
        }
    #endif
    };

    //-------------------------------------------------------------------------
    /// extractor to evaluate iterator range and convert using stringstream
    //-------------------------------------------------------------------------
    template<class T>
    class e_value_t
    {
        T& t_;
    public:
        explicit e_value_t(T& t) : t_(t) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2)const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            static_assert(std::is_constructible_v<std::basic_string<char_t>, Iterator, Iterator>);
            std::operator>>(std::basic_istringstream<char_t>(std::basic_string<char_t>(i1, i2)), t_);
        }
    };

    //-------------------------------------------------------------------------
    /// extractor to evaluate iterator range specialized for std::basic_string
    //-------------------------------------------------------------------------
    template<class C, class T, class A>
    class e_value_t<std::basic_string<C, T, A>>
    {
        std::basic_string<C, T, A>& str;
    public:
        explicit e_value_t(std::basic_string<C, T, A>& str) : str(str) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(std::is_convertible_v<decltype(*i1), C>);
            str.assign(i1, i2);
        }
    };

    //-------------------------------------------------------------------------
    /// extractor to evaluate iterator range specialized for std::basic_string_view
    //-------------------------------------------------------------------------
    template<class C, class T>
    class e_value_t<std::basic_string_view<C, T>>
    {
        std::basic_string_view<C, T>& str;
    public:
        explicit e_value_t(std::basic_string_view<C, T>& str) : str(str) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(std::is_convertible_v<decltype(*i1), C>);
            str = std::basic_string_view<C, T>(i1, std::distance(i1,i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for single character
    //-------------------------------------------------------------------------
    template<class T>
    class elem_ext_t
    {
        T& c;
    public:
        explicit elem_ext_t(T& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(std::is_convertible_v<decltype(*i1), T>);
            if (i1 != i2)
                c = static_cast<T>(*i1);
        }
    };

    //-------------------------------------------------------------------------
    template<>
    class e_value_t<std::byte> : public elem_ext_t<std::byte>
    {
    public:
        using elem_ext_t<std::byte>::elem_ext_t;
        using elem_ext_t<std::byte>::operator();
    };

    //-------------------------------------------------------------------------
    template<>
    class e_value_t<char> : public elem_ext_t<char>
    {
    public:
        using elem_ext_t<char>::elem_ext_t;
        using elem_ext_t<char>::operator();
    };

    //-------------------------------------------------------------------------
    template<>
    class e_value_t<signed char> : public elem_ext_t<signed char>
    {
    public:
        using elem_ext_t<signed char>::elem_ext_t;
        using elem_ext_t<signed char>::operator();
    };

    //-------------------------------------------------------------------------
    template<>
    class e_value_t<unsigned char> : public elem_ext_t<unsigned char>
    {
    public:
        using elem_ext_t<unsigned char>::elem_ext_t;
        using elem_ext_t<unsigned char>::operator();
    };

    //-------------------------------------------------------------------------
    template<>
    class e_value_t<wchar_t> : public elem_ext_t<wchar_t>
    {
    public:
        using elem_ext_t<wchar_t>::elem_ext_t;
        using elem_ext_t<wchar_t>::operator();
    };


    //-------------------------------------------------------------------------
    // extractors for decimal values
    //-------------------------------------------------------------------------
    /// extractor for int
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<short>
    {
        short& c;
    public:
        explicit e_value_t(short& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if(i1 != i2)
                c = std::stoi(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for unsigned int
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<unsigned short>
    {
        unsigned short& c;
    public:
        explicit e_value_t(unsigned short& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if(i1 != i2)
                c = std::stoul(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for int
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<int>
    {
        int& c;
    public:
        explicit e_value_t(int& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if(i1 != i2)
                c = std::stoi(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for unsigned int
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<unsigned int>
    {
        unsigned int& c;
    public:
        explicit e_value_t(unsigned int& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if(i1 != i2)
                c = std::stoul(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for long
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<long>
    {
        long& c;
    public:
        explicit e_value_t(long& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if (i1 != i2)
                c = std::stol(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for unsigned long
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<unsigned long>
    {
        unsigned long& c;
    public:
        explicit e_value_t(unsigned long& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if (i1 != i2)
                c = std::stoul(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for long long
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<long long>
    {
        long long& c;
    public:
        explicit e_value_t(long long& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if (i1 != i2)
                c = std::stoll(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for long long
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<unsigned long long>
    {
        unsigned long long& c;
    public:
        explicit e_value_t(unsigned long long& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if (i1 != i2)
                c = std::stoull(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for float
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<float>
    {
        float& c;
    public:
        explicit e_value_t(float& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if (i1 != i2)
                c = std::stof(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for double
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<double>
    {
        double& c;
    public:
        explicit e_value_t(double& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if (i1 != i2)
                c = std::stod(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for long double
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<long double>
    {
        long double& c;
    public:
        explicit e_value_t(long double& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            using char_t = typename std::iterator_traits<Iterator>::value_type;
            if (i1 != i2)
                c = std::stold(std::basic_string<char_t>(i1, i2));
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for bool
    //-------------------------------------------------------------------------
    template<>
    class e_value_t<bool>
    {
        bool& c;
    public:
        explicit e_value_t(bool& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            c = i1 != i2;
        }
    };

    //-------------------------------------------------------------------------
    /// extractor for basic_ostream
    //-------------------------------------------------------------------------
    template<class Elem, class Traits>
    class e_value_t<std::basic_ostream<Elem, Traits>>
    {
        std::basic_ostream<Elem, Traits>& s;
    public:
        explicit e_value_t(std::basic_ostream<Elem, Traits>& s) : s(s) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
            for(; i1 != i2; i1 = std::next(i1))
                s << *i1;
        }
    };

    //-------------------------------------------------------------------------
    /// reference wrapper for extractor (backward compatibility with v.1)
    //-------------------------------------------------------------------------
    template<class T>
    class e_ref
    {
        T t_;
    public:
		template<class TT, class = detail::disable_copy<e_ref<T>, TT>>
        explicit e_ref(TT&& t) : t_(std::forward<TT>(t)) {}

        template<class I>
        void operator()(I i1, I i2) const
        {
            t_(i1, i2);
        }
    };

    template<class T>
    explicit e_ref(T&&)->e_ref<std::decay_t<T>>;

    //-------------------------------------------------------------------------
    /// extractor for iterator range to determine its length
    //-------------------------------------------------------------------------
    template<class S>
    class e_length
    {
        S& length;
    public:
        explicit e_length(S& length) : length(length) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
			static_assert(std::is_convertible_v<size_t, S>);
            length = static_cast<S>(std::distance(i1, i2));
        }
    };

    template<class S>
    explicit e_length(S&)->e_length<S>;

    //-------------------------------------------------------------------------
    /// extractor for iterator range that pushes values to container
    //-------------------------------------------------------------------------
    template<class C>
    class e_push_back
    {
        C& c;
        using value_type = typename C::value_type;
    public:
        explicit e_push_back(C& c) : c(c) {}

        template<class Iterator, class Iterator2>
        void operator() (Iterator i1, Iterator2 i2) const
        {
			value_type value{};
			e_value_t<value_type>{value}(i1, i2);
            c.emplace_back(std::move(value));
        }
    };

    template<class C>
    explicit e_push_back(C&)->e_push_back<C>;
}
